package span

import (
	"fmt"
	"mi/internal/xmath"
)

func GetLineStarts(data []byte) []int {
	var lines []int

	var line = 0
	for offset, b := range data {
		if b == '\n' {
			lines = append(lines, line)
			line = offset + 1
		}
	}
	lines = append(lines, line)
	return lines
}

type TextRange struct {
	Start int
	End   int
}

type TextSpan struct {
	Start int
	Length int
}

func CreateTextSpan(start int, length int) TextSpan {
	return TextSpan{
		Start:  start,
		Length: length,
	}
}

func CreateTextSpanFromBounds(start int, end int) TextSpan {
	return CreateTextSpan(start, end - start)
}

func TextSpanEnd(span TextSpan) int {
	return span.Start + span.Length
}

func PositionFromOffset(offset int, content []byte) (Position, error) {
	return PositionFromOffsetWithCache(offset, content, GetLineStarts(content))
}

func PositionFromOffsetWithCache(offset int, content []byte, lineStarts []int) (Position, error) {
	if offset > len(content) {
		return Position{}, fmt.Errorf("PositionFromOffsetWithCache offset out of content")
	}

	var lineNumber = xmath.BinarySearch(lineStarts, offset)
	if lineNumber < 0 {
		lineNumber = ^lineNumber - 1
	}
	return Position{
		Line:   lineNumber,
		Column: offset - lineStarts[lineNumber],
	}, nil
}

func UTF8PositionFromOffset(offset int, content []byte) (UTF8Position, error) {
	var lineStarts = GetLineStarts(content)
	return UTF8PositionFromOffsetWithCache(offset, content, lineStarts)
}

func UTF8PositionFromOffsetWithCache(offset int, content []byte, lineStarts []int) (UTF8Position, error) {
	p, err := PositionFromOffsetWithCache(offset, content, lineStarts)
	if err != nil {
		return UTF8Position{}, err
	}
	u8p, err := p.ToUTF8PositionWithCache(content, lineStarts)
	if err != nil {
		return UTF8Position{}, err
	}
	return u8p, err
}

func UTF16PositionFromOffset(offset int, content []byte) (UTF16Position, error) {
	return UTF16PositionFromOffsetWithCache(offset, content, GetLineStarts(content))
}

func UTF16PositionFromOffsetWithCache(offset int, content []byte, lineStarts []int) (UTF16Position, error) {
	p, err := PositionFromOffsetWithCache(offset, content, lineStarts)
	if err != nil {
		return UTF16Position{}, err
	}
	u16p, err := p.ToUTF16PositionWithCache(content, lineStarts)
	if err != nil {
		return UTF16Position{}, err
	}
	return u16p, err
}

func SpanFromOffset(start int, end int, content []byte) (Span, error) {
	return SpanFromOffsetWithCache(start, end, content, GetLineStarts(content))
}

func SpanFromOffsetWithCache(start int, end int, content []byte, lineStarts []int) (Span, error) {
	s, err := PositionFromOffsetWithCache(start, content, lineStarts)
	if err != nil {
		return Span{}, err
	}
	e, err := PositionFromOffsetWithCache(end, content, lineStarts)
	if err != nil {
		return Span{}, err
	}
	return Span{
		Start: s,
		End:   e,
	}, nil
}

func UTF8SpanFromOffset(start int, end int, content []byte) (UTF8Span, error) {
	return UTF8SpanFromOffsetWithCache(start, end, content, GetLineStarts(content))
}

func UTF8SpanFromOffsetWithCache(start int, end int, content []byte, lineStarts []int) (UTF8Span, error) {
	span, err := SpanFromOffsetWithCache(start, end, content, lineStarts)
	if err != nil {
		return UTF8Span{}, err
	}
	u8span, err := span.ToUTF8SpanWithCache(content, lineStarts)
	if err != nil {
		return UTF8Span{}, err
	}
	return u8span, nil
}

func UTF16SpanFromOffset(start int, end int, content []byte) (UTF16Span, error) {
	return UTF16SpanFromOffsetWithCache(start, end, content, GetLineStarts(content))
}

func UTF16SpanFromOffsetWithCache(start int, end int, content []byte, lineStarts []int) (UTF16Span, error) {
	span, err := SpanFromOffsetWithCache(start, end, content, lineStarts)
	if err != nil {
		return UTF16Span{}, err
	}
	u16span, err := span.ToUTF16SpanWithCache(content, lineStarts)
	if err != nil {
		return UTF16Span{}, err
	}
	return u16span, nil
}
